popover: Track toplevel focus changes
authorCarlos Garnacho <carlosg@gnome.org>
Thu, 6 Mar 2014 13:50:35 +0000 (14:50 +0100)
committerCarlos Garnacho <carlosg@gnome.org>
Thu, 6 Mar 2014 22:23:47 +0000 (23:23 +0100)
Make the popover temporarily undo the GTK+ grab, so it remains modal
to its window, but does not attempt to steal focus on other non-modal
windows that get the focus.

This was most confusing with keyboard navigation, as the focus would
remain stuck on the popover, and not move to the newly focused window
after the popover was dismissed. It didn't have as much effect on
pointer operations as only the first click would be consumed in order
to hide the popover.

gtk/gtkpopover.c

index 1cc9e9c5d4525fac30a04d31ec89268de79a79c6..2286cff8f49ce316cfa7b167de7fe3d0756dfc5a 100644 (file)
@@ -102,6 +102,7 @@ struct _GtkPopoverPrivate
   guint modal              : 1;
   guint button_pressed     : 1;
   guint apply_shape        : 1;
+  guint grab_notify_blocked : 1;
 };
 
 static GQuark quark_widget_popovers = 0;
@@ -254,6 +255,52 @@ gtk_popover_realize (GtkWidget *widget)
   gtk_widget_set_realized (widget, TRUE);
 }
 
+static gboolean
+window_focus_in (GtkWidget  *widget,
+                 GdkEvent   *event,
+                 GtkPopover *popover)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  /* Regain the grab when the window is focused */
+  if (priv->modal &&
+      gtk_widget_is_drawable (GTK_WIDGET (popover)))
+    {
+      GtkWidget *focus;
+
+      gtk_grab_add (GTK_WIDGET (popover));
+
+      focus = gtk_window_get_focus (GTK_WINDOW (widget));
+
+      if (!gtk_widget_is_ancestor (focus, GTK_WIDGET (popover)))
+        gtk_widget_grab_focus (GTK_WIDGET (popover));
+
+      if (priv->grab_notify_blocked)
+        g_signal_handler_unblock (priv->widget, priv->grab_notify_id);
+
+      priv->grab_notify_blocked = FALSE;
+    }
+  return FALSE;
+}
+
+static gboolean
+window_focus_out (GtkWidget  *widget,
+                  GdkEvent   *event,
+                  GtkPopover *popover)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  /* Temporarily remove the grab when unfocused */
+  if (priv->modal &&
+      gtk_widget_is_drawable (GTK_WIDGET (popover)))
+    {
+      g_signal_handler_block (priv->widget, priv->grab_notify_id);
+      gtk_grab_remove (GTK_WIDGET (popover));
+      priv->grab_notify_blocked = TRUE;
+    }
+  return FALSE;
+}
+
 static void
 gtk_popover_apply_modality (GtkPopover *popover,
                             gboolean    modal)
@@ -270,9 +317,15 @@ gtk_popover_apply_modality (GtkPopover *popover,
         g_object_ref (prev_focus);
       gtk_grab_add (GTK_WIDGET (popover));
       gtk_widget_grab_focus (GTK_WIDGET (popover));
+
+      g_signal_connect (priv->window, "focus-in-event",
+                        G_CALLBACK (window_focus_in), popover);
+      g_signal_connect (priv->window, "focus-out-event",
+                        G_CALLBACK (window_focus_out), popover);
     }
   else
     {
+      g_signal_handlers_disconnect_by_data (priv->window, popover);
       gtk_grab_remove (GTK_WIDGET (popover));
 
       if (priv->prev_focus_widget)